package info.u250.c2d.box2deditor.gdx.support;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.math.EarClippingTriangulator;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.FloatArray;
import com.badlogic.gdx.utils.ShortArray;
import info.u250.c2d.box2d.Box2dObject;
import info.u250.c2d.box2d.model.b2BodyDefModel;
import info.u250.c2d.box2d.model.b2JointDefModel;
import info.u250.c2d.box2d.model.fixture.b2CircleFixtureDefModel;
import info.u250.c2d.box2d.model.fixture.b2RectangleFixtureDefModel;
import info.u250.c2d.box2d.model.joint.b2DistanceJointDefModel;
import info.u250.c2d.box2d.model.joint.b2GearJointDefModel;
import info.u250.c2d.box2d.model.joint.b2PulleyJointDefModel;
import info.u250.c2d.box2d.model.joint.b2RopeJointDefModel;
import info.u250.c2d.box2deditor.adapter.*;
import info.u250.c2d.engine.Engine;
import info.u250.c2d.utils.Mathutils;

public class Geometry {
    public static final Color COLOR_DistanceJoint = new Color(Color.RED);
    public static final Color COLOR_PrismaticJoint = new Color(Color.GREEN);
    public static final Color COLOR_RevoluteJoint = new Color(Color.MAGENTA);
    public static final Color COLOR_RopeJoint = new Color(Color.ORANGE);
    public static final Color COLOR_WeldJoint = new Color(Color.YELLOW);
    public static final Color COLOR_WheelJoint = new Color(Color.WHITE);
    public static final Color COLOR_PulleyJoint = new Color(Color.CYAN);
    public static final Color COLOR_FrictionJoint = new Color(Color.GRAY);

    private static final Color CIRCLE = new Color(1, 0.5f, 1, 0.4f);
    private static final Color CIRCLE_BORDER = new Color(1, 0.5f, 1, 0.8f);
    private static final Color LINE = new Color(1f, 0.5f, 1f, 1f);
    private static final Color BOX = new Color(0.5f, 1, 1f, 0.4f);
    private static final Color BOX_BORDER = new Color(0.1f, 0.8f, 1f, 0.9f);
    private static final Color POLYGON = new Color(0f, 1, 0.1f, 0.2f);
    private static final Color POLYGON_BORDER = new Color(0f, 0f, 0f, 1f);

    private static final Color FocusColor = new Color(0, 0, 1f, 0.3f);
    private static final Color FocusColor_BORDER = new Color(0, 0, 1, 0.8f);
    private static final EarClippingTriangulator earClippingTriangulator = new EarClippingTriangulator();

    public static void renderPolygon(PolygonFixtureDefModel polygon, Vector2 position, float angle, boolean focus) {
        ShapeRenderer render = Engine.getShapeRenderer();
        //rotate
        final float cos = MathUtils.cosDeg(angle);
        final float sin = MathUtils.sinDeg(angle);

        final FloatArray vertices = new FloatArray();
        // rotate if needed
        for (Vector2 v : polygon.polygon) {
            if (angle != 0) {
                vertices.add(cos * v.x - sin * v.y + position.x);
                vertices.add(sin * v.x + cos * v.y + position.y);
            } else {
                vertices.add(v.x + position.x);
                vertices.add(v.y + position.y);
            }
        }

        ShortArray temp = earClippingTriangulator.computeTriangles(vertices);
        for (int i = 0; i < temp.size; i += 3) {
            render.setColor(focus ? FocusColor : POLYGON);
            render.begin(ShapeType.Filled);
            render.triangle(vertices.get(2 * temp.get(i)), vertices.get(2 * temp.get(i) + 1), vertices.get(2 * temp.get(i + 1)), vertices.get(2 * temp.get(i + 1) + 1), vertices.get(2 * temp.get(i + 2)), vertices.get(2 * temp.get(i + 2) + 1));
            render.end();
            render.setColor(Color.CYAN);
            render.begin(ShapeType.Line);
            render.triangle(vertices.get(2 * temp.get(i)), vertices.get(2 * temp.get(i) + 1), vertices.get(2 * temp.get(i + 1)), vertices.get(2 * temp.get(i + 1) + 1), vertices.get(2 * temp.get(i + 2)), vertices.get(2 * temp.get(i + 2) + 1));
            render.end();
        }

        render.setColor(focus ? FocusColor_BORDER : POLYGON_BORDER);
        render.begin(ShapeType.Line);
        float sx = 0;
        float sy = 0;
        float fx = 0;
        float fy = 0;
        int vertexCount = vertices.size / 2;
        for (int i = 0; i < vertexCount; i++) {
            float x = vertices.get(2 * i);
            float y = vertices.get(2 * i + 1);
            if (i == 0) {
                sx = fx = x;
                sy = fy = y;
                continue;
            }
            render.line(sx, sy, x, y);
            sx = x;
            sy = y;
        }
        render.line(fx, fy, sx, sy);
        render.end();

    }

    public static void renderBox(b2RectangleFixtureDefModel box, Vector2 position, float angle, boolean focus) {
        ShapeRenderer render = Engine.getShapeRenderer();

        float rect_angle = new Vector2(box.width, box.height).angle();
        float half_diagonal = (float) Math.sqrt(box.width * box.width + box.height * box.height) / 2;

        render.setColor(focus ? FocusColor : BOX);
        render.begin(ShapeType.Filled);
        render.triangle(
                position.x + MathUtils.cosDeg(180 + rect_angle + angle) * half_diagonal,
                position.y + MathUtils.sinDeg(180 + rect_angle + angle) * half_diagonal,
                position.x + MathUtils.cosDeg(-rect_angle + angle) * half_diagonal,
                position.y + MathUtils.sinDeg(-rect_angle + angle) * half_diagonal,
                position.x + MathUtils.cosDeg(rect_angle + angle) * half_diagonal,
                position.y + MathUtils.sinDeg(rect_angle + angle) * half_diagonal);
        render.triangle(
                position.x + MathUtils.cosDeg(180 - rect_angle + angle) * half_diagonal,
                position.y + MathUtils.sinDeg(180 - rect_angle + angle) * half_diagonal,
                position.x + MathUtils.cosDeg(180 + rect_angle + angle) * half_diagonal,
                position.y + MathUtils.sinDeg(180 + rect_angle + angle) * half_diagonal,
                position.x + MathUtils.cosDeg(rect_angle + angle) * half_diagonal,
                position.y + MathUtils.sinDeg(rect_angle + angle) * half_diagonal);
        render.end();
        render.setColor(focus ? FocusColor_BORDER : BOX_BORDER);
        render.begin(ShapeType.Line);
        render.triangle(
                position.x + MathUtils.cosDeg(180 + rect_angle + angle) * half_diagonal,
                position.y + MathUtils.sinDeg(180 + rect_angle + angle) * half_diagonal,
                position.x + MathUtils.cosDeg(-rect_angle + angle) * half_diagonal,
                position.y + MathUtils.sinDeg(-rect_angle + angle) * half_diagonal,
                position.x + MathUtils.cosDeg(rect_angle + angle) * half_diagonal,
                position.y + MathUtils.sinDeg(rect_angle + angle) * half_diagonal);
        render.triangle(
                position.x + MathUtils.cosDeg(180 - rect_angle + angle) * half_diagonal,
                position.y + MathUtils.sinDeg(180 - rect_angle + angle) * half_diagonal,
                position.x + MathUtils.cosDeg(180 + rect_angle + angle) * half_diagonal,
                position.y + MathUtils.sinDeg(180 + rect_angle + angle) * half_diagonal,
                position.x + MathUtils.cosDeg(rect_angle + angle) * half_diagonal,
                position.y + MathUtils.sinDeg(rect_angle + angle) * half_diagonal);
        render.end();
    }

    public static void renderCircle(b2CircleFixtureDefModel circle, Vector2 position, float angle, boolean focus) {
        ShapeRenderer render = Engine.getShapeRenderer();
        render.setColor(focus ? FocusColor : CIRCLE);
        render.begin(ShapeType.Filled);
        render.circle(position.x, position.y, circle.radius);
        render.end();
        render.setColor(focus ? FocusColor_BORDER : CIRCLE_BORDER);
        render.begin(ShapeType.Line);
        render.circle(position.x, position.y, circle.radius);
        render.end();
        render.setColor(LINE);
        render.begin(ShapeType.Line);
        render.line(
                position.x, position.y,
                position.x + circle.radius * MathUtils.cosDeg(angle),
                position.y + circle.radius * MathUtils.sinDeg(angle));
        render.end();
    }

    public static void ajustJoint(b2JointDefModel object) {
        try {
            if (object instanceof WeldJointDefModel) {
                WeldJointDefModel model = WeldJointDefModel.class.cast(object);
                model.localAnchorA.set(model.bodyA.body.getLocalPoint(model.anchor.cpy().scl(1f / Box2dObject.RADIO)).scl(Box2dObject.RADIO));
                model.localAnchorB.set(model.bodyB.body.getLocalPoint(model.anchor.cpy().scl(1f / Box2dObject.RADIO)).scl(Box2dObject.RADIO));
                model.referenceDegrees = model.bodyB.degrees - model.bodyA.degrees;
            } else if (object instanceof RevoluteJointDefModel) {
                RevoluteJointDefModel model = RevoluteJointDefModel.class.cast(object);
                if (model.useBodyACenter) {
                    model.anchor.set(model.bodyA.position);
                } else {
                    if (model.useBodyBCenter) {
                        model.anchor.set(model.bodyB.position);
                    }
                }
                model.localAnchorA.set(model.bodyA.body.getLocalPoint(model.anchor.cpy().scl(1f / Box2dObject.RADIO)).scl(Box2dObject.RADIO));
                model.localAnchorB.set(model.bodyB.body.getLocalPoint(model.anchor.cpy().scl(1f / Box2dObject.RADIO)).scl(Box2dObject.RADIO));
                model.referenceDegrees = model.bodyB.degrees - model.bodyA.degrees;
            } else if (object instanceof PrismaticJointDefModel) {
                PrismaticJointDefModel model = PrismaticJointDefModel.class.cast(object);
                if (model.useBodyACenter) {
                    model.anchor.set(model.bodyA.position);
                } else {
                    if (model.useBodyBCenter) {
                        model.anchor.set(model.bodyB.position);
                    }
                }
                if (model.useABCenterLine) {
                    model.localAxisA.set(model.bodyB.position).sub(model.bodyA.position);
                    model.localAxisA.scl(1f / model.localAxisA.len());
                }

                model.localAnchorA.set(model.bodyA.body.getLocalPoint(model.anchor.cpy().scl(1f / Box2dObject.RADIO)).scl(Box2dObject.RADIO));
                model.localAnchorB.set(model.bodyB.body.getLocalPoint(model.anchor.cpy().scl(1f / Box2dObject.RADIO)).scl(Box2dObject.RADIO));
                model.referenceDegrees = model.bodyB.degrees - model.bodyA.degrees;
            } else if (object instanceof FrictionJointDefModel) {
                FrictionJointDefModel model = FrictionJointDefModel.class.cast(object);
                if (model.useBodyACenter) {
                    model.anchor.set(model.bodyA.position);
                } else {
                    if (model.useBodyBCenter) {
                        model.anchor.set(model.bodyB.position);
                    }
                }
                model.localAnchorA.set(model.bodyA.body.getLocalPoint(model.anchor.cpy().scl(1f / Box2dObject.RADIO)).scl(Box2dObject.RADIO));
                model.localAnchorB.set(model.bodyB.body.getLocalPoint(model.anchor.cpy().scl(1f / Box2dObject.RADIO)).scl(Box2dObject.RADIO));
            } else if (object instanceof WheelJointDefModel) {
                WheelJointDefModel model = WheelJointDefModel.class.cast(object);
                if (model.useBodyACenter) {
                    model.anchor.set(model.bodyA.position);
                } else {
                    if (model.useBodyBCenter) {
                        model.anchor.set(model.bodyB.position);
                    }
                }
                if (model.useABCenterLine) {
                    model.localAxisA.set(model.bodyB.position).sub(model.bodyA.position);
                }
                model.localAxisA.scl(1f / model.localAxisA.len());
                model.localAnchorA.set(model.bodyA.body.getLocalPoint(model.anchor.cpy().scl(1f / Box2dObject.RADIO)).scl(Box2dObject.RADIO));
                model.localAnchorB.set(model.bodyB.body.getLocalPoint(model.anchor.cpy().scl(1f / Box2dObject.RADIO)).scl(Box2dObject.RADIO));
            } else if (object instanceof PulleyJointDefModel) {
                PulleyJointDefModel model = PulleyJointDefModel.class.cast(object);
                if (model.setBodyAZero) {
                    model.localAnchorA.set(0, 0);
                }
                if (model.setBodyBZero) {
                    model.localAnchorB.set(0, 0);
                }
                final Vector2 worldA = model.bodyA.body.getWorldPoint(model.localAnchorA.cpy().scl(1f / Box2dObject.RADIO)).scl(Box2dObject.RADIO);
                final Vector2 worldB = model.bodyB.body.getWorldPoint(model.localAnchorB.cpy().scl(1f / Box2dObject.RADIO)).scl(Box2dObject.RADIO);
                if (model.groundAAlignAnchorA) {
                    model.groundAnchorA.x = worldA.x;
                }
                if (model.groundBAlignAnchorB) {
                    model.groundAnchorB.x = worldB.x;
                }
                model.lengthA = worldA.dst(model.groundAnchorA);
                model.lengthB = worldB.dst(model.groundAnchorB);
            } else if (object instanceof RopeJointDefModel) {
                RopeJointDefModel model = RopeJointDefModel.class.cast(object);
                if (model.setBodyAZero) {
                    model.localAnchorA.set(0, 0);
                }
                if (model.setBodyBZero) {
                    model.localAnchorB.set(0, 0);
                }
                if (model.autoCalculateLength) {
                    model.maxLength =
                            model.bodyA.body.getWorldPoint(model.localAnchorA.cpy().scl(1f / Box2dObject.RADIO)).sub(
                                    model.bodyB.body.getWorldPoint(model.localAnchorB.cpy().scl(1f / Box2dObject.RADIO))
                            ).scl(Box2dObject.RADIO).len();
                }
            } else if (object instanceof DistanceJointDefModel) {
                DistanceJointDefModel model = DistanceJointDefModel.class.cast(object);
                if (model.setBodyAZero) {
                    model.localAnchorA.set(0, 0);
                }
                if (model.setBodyBZero) {
                    model.localAnchorB.set(0, 0);
                }
                if (model.autoCalculateLength) {
                    model.length =
                            model.bodyA.body.getWorldPoint(model.localAnchorA.cpy().scl(1f / Box2dObject.RADIO)).sub(
                                    model.bodyB.body.getWorldPoint(model.localAnchorB.cpy().scl(1f / Box2dObject.RADIO))
                            ).scl(Box2dObject.RADIO).len();
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }

    }

    public static void splitPolygon(PolygonFixtureDefModel object) {
        object.vertices.clear();
        final FloatArray vertices = new FloatArray();
        for (Vector2 v : object.polygon) {
            vertices.add(v.x);
            vertices.add(v.y);
        }
        ShortArray temp = earClippingTriangulator.computeTriangles(vertices);
        for (int i = 0; i < temp.size; i += 3) {
            Vector2[] vv = new Vector2[3];
            vv[0] = new Vector2(vertices.get(2 * temp.get(i)), vertices.get(2 * temp.get(i) + 1));
            vv[1] = new Vector2(vertices.get(2 * temp.get(i + 1)), vertices.get(2 * temp.get(i + 1) + 1));
            vv[2] = new Vector2(vertices.get(2 * temp.get(i + 2)), vertices.get(2 * temp.get(i + 2) + 1));
            if (Mathutils.isClockwise(vv[0], vv[1], vv[2])) {
                Vector2 vt = vv[0];
                vv[0] = vv[2];
                vv[2] = vt;
            }
            object.vertices.add(vv);
        }
    }

    public static void renderJoint(b2JointDefModel b2Joint) {
        if (b2Joint.bodyA == null || b2Joint.bodyB == null) return;
        if (b2Joint.bodyA.body == null || b2Joint.bodyB.body == null) return;

        ShapeRenderer render = Engine.getShapeRenderer();
        if (b2Joint instanceof b2DistanceJointDefModel) {
            final b2DistanceJointDefModel obj = b2DistanceJointDefModel.class.cast(b2Joint);
            final Vector2 p1 = worldPoint(obj.bodyA, obj.localAnchorA);
            final Vector2 p2 = worldPoint(obj.bodyB, obj.localAnchorB);
            render.begin(ShapeType.Line);
            render.line(p1.x, p1.y, p2.x, p2.y);
            render.end();
        } else if (b2Joint instanceof b2PulleyJointDefModel) {
            final b2PulleyJointDefModel obj = b2PulleyJointDefModel.class.cast(b2Joint);
            Vector2 s1 = obj.groundAnchorA;
            Vector2 s2 = obj.groundAnchorB;
            final Vector2 p1 = worldPoint(obj.bodyA, obj.localAnchorA);
            final Vector2 p2 = worldPoint(obj.bodyB, obj.localAnchorB);
            render.begin(ShapeType.Line);
            render.line(s1.x, s1.y, p1.x, p1.y);
            render.line(s2.x, s2.y, p2.x, p2.y);
            render.line(s1.x, s1.y, s2.x, s2.y);
            render.end();
        } else if (b2Joint instanceof PrismaticJointDefModel) {
            final PrismaticJointDefModel obj = PrismaticJointDefModel.class.cast(b2Joint);
            Vector2 x1 = obj.bodyA.position;
            Vector2 x2 = obj.bodyB.position;
            Vector2 anchor = obj.anchor;
            render.begin(ShapeType.Line);
            render.line(x1.x, x1.y, anchor.x, anchor.y);
            render.line(x2.x, x2.y, anchor.x, anchor.y);
            render.end();
        } else if (b2Joint instanceof RevoluteJointDefModel) {
            final RevoluteJointDefModel obj = RevoluteJointDefModel.class.cast(b2Joint);
            Vector2 x1 = obj.bodyA.position;
            Vector2 x2 = obj.bodyB.position;
            Vector2 anchor = obj.anchor;
            render.begin(ShapeType.Line);
            render.line(x1.x, x1.y, anchor.x, anchor.y);
            render.line(x2.x, x2.y, anchor.x, anchor.y);
            render.end();
        } else if (b2Joint instanceof b2RopeJointDefModel) {
            final b2RopeJointDefModel obj = b2RopeJointDefModel.class.cast(b2Joint);
            Vector2 x1 = obj.bodyA.position;
            Vector2 x2 = obj.bodyB.position;
            final Vector2 p1 = worldPoint(obj.bodyA, obj.localAnchorA);
            final Vector2 p2 = worldPoint(obj.bodyB, obj.localAnchorB);
            render.begin(ShapeType.Line);
            render.line(x1.x, x1.y, p1.x, p1.y);
            render.line(p1.x, p1.y, p2.x, p2.y);
            render.line(x2.x, x2.y, p2.x, p2.y);
            render.end();
        } else if (b2Joint instanceof WeldJointDefModel) {
            final WeldJointDefModel obj = WeldJointDefModel.class.cast(b2Joint);
            Vector2 x1 = obj.bodyA.position;
            Vector2 x2 = obj.bodyB.position;
            Vector2 anchor = obj.anchor;
            render.begin(ShapeType.Line);
            render.line(x1.x, x1.y, anchor.x, anchor.y);
            render.line(x2.x, x2.y, anchor.x, anchor.y);
            render.end();
        } else if (b2Joint instanceof WheelJointDefModel) {
            final WheelJointDefModel obj = WheelJointDefModel.class.cast(b2Joint);
            Vector2 x1 = obj.bodyA.position;
            Vector2 x2 = obj.bodyB.position;
            Vector2 anchor = obj.anchor;
            render.begin(ShapeType.Line);
            render.line(x1.x, x1.y, anchor.x, anchor.y);
            render.line(x2.x, x2.y, anchor.x, anchor.y);
            render.end();
        } else if (b2Joint instanceof FrictionJointDefModel) {
            final FrictionJointDefModel obj = FrictionJointDefModel.class.cast(b2Joint);
            Vector2 x1 = obj.bodyA.position;
            Vector2 x2 = obj.bodyB.position;
            Vector2 anchor = obj.anchor;
            render.begin(ShapeType.Line);
            render.line(x1.x, x1.y, anchor.x, anchor.y);
            render.line(x2.x, x2.y, anchor.x, anchor.y);
            render.end();
        } else if (b2Joint instanceof b2GearJointDefModel) {
            //TODO
        }
    }

    static Vector2 worldPoint(b2BodyDefModel b2, Vector2 localVector) {
        Vector2 tmp = new Vector2();
        try {
            tmp.set(b2.body.getWorldPoint(localVector.cpy().scl(1f / Box2dObject.RADIO))).scl(Box2dObject.RADIO);
        } catch (Exception ex) {
            //
        }
        return tmp;
    }
}
